
--------------------------------------------------------------------------------------------------
-- Global SKG variables
--------------------------------------------------------------------------------------------------
-- 10/04/07 Trellis /A	Modified to support Double Suicide Kings
--				  Changee bidding functions to support DSK.
-- 12/10/07 Trellis /B	Modified tie breaker code to put B position at the next lowest position where the roll number does not exist.
--				  Also moves A position to the next highest position where the roll number does not exist.
--				  Changed to allow random roll of 500-1000.
-- 05/01/08 Trellis /C	Modified to add option of minimum roll number to be set
--				  under options.
-- 08/25/08 Trellis /D	Removed call to SKG_Net_VerboseSuicide as the number of positions that need to move become too great, it
--				  will cause a disconnect.  SendAddonMessage will disconnect the client if the message being sent is greater then
--				  254 characters.
-- 10/21/08 Trellis /E	Fixed calls to ChatFrame_OnEvent argument call to Bliz changes
-- 02/22/09 Trellis /F	Changed Minimum roll update to not require an Enter key press to save
-- 03/27/09 Trellis /G	Changed the default spam max to 40 and fixed the SKG_FrameDropDown() args.
-- 05/08/09 Trellis /H	Added option to only reply with positions on the currently selected list.
-- 05/20/09 Trellis /I	Added option to sync to Raid and/or Guild
-- 06/20/09 Trellis /J	Included class to sync message
-- 07/14/09 Trellis /K	Added incorrect bid format error message
-- 08/20/09 Trellis /L	Include class on new roll ins and changed list spam whispers to utilize a queue
-- 11/30/09 Trellis /M	Minor fix to remove position letter in high bidder chat when high bidder retracts bid
-- 02/05/10 Trellis /N	Changed the insert command pattern to handle special characters properly
-- 10/05/10 Trellis /O	Fix bid processing to handle bid position letter correctly.  Turn off sticky channel during whispers.
-- 10/13/10 Eledryn /P  4.0 updates, reannounce name of item on bid close (English locale only)
-- 04/09/11 Trellis /Q	Fix new list dialog box (removing getglobal())
-- 05/01/11 Trellis /R	Include RegisterAddonMessagePrefix, modified sync messages for GUILD/RAID channel
-- 08/28/12 Trellis /S	v5.0.4 API changes
--------------------------------------------------------------------------------------------------
SKG_VERSION = 1024; -- version 

SKG_ITEM_HEIGHT = 16;
SKG_ITEMS_SHOWN = 23;
SKG_JUNK_LABEL = "Junk";
--SKG_ROLL_MIN = 1;
SKG_ROLL_MIN_1 = 1;  --/B
SKG_ROLL_MIN_500 = 500;  --/B
SKG_ROLL_MAX = 1000;
SKG_POPUP = "SKG_EDIT";
SKG_CONFIRM="SKG_CONFIRM";
--SKG_DEBUG = true;
SKG_DEBUG = false;
SKG_CHANNEL_PREFIX = "SuicKingGeo";
--SKG_CHANNEL_PREFIX = "SKG_DSK";
SKG_STICKY_WHISPER = nil;   --/O

-- UIPanelWindows["SKG_Frame"] =		{ area = "left",	pushable = 11 };

local l_isCaptureActive = 0;
local l_currentRollList = { };
local l_loadedList = nil;
local l_undoList = { };
local l_reserveList = { };
local l_supervisor = false;
local l_bidOpen = false;
local l_highBidders = { };
local l_inRaid = false;
local l_currentLootLink = nil;  --/P

AddonChanReg = RegisterAddonMessagePrefix(SKG_CHANNEL_PREFIX);  --/R
if (AddonChanReg == false or AddonChanReg == nil) then
	SKG_print('Unable to register for Addon Messages');
end

StaticPopupDialogs[SKG_POPUP] = {
	text = SKG_MSG_SAVE,
	button1 = TEXT(ACCEPT),
	button2 = TEXT(CANCEL),
	hasEditBox = 1,
	maxLetters = 16,
	OnAccept = function(self)  --/P
		local newList = self.editBox:GetText();  --/Q
		SKG_OnMessageBoxAccept(newList);  --/Q
	end,
	OnShow = function(self)
		_G[self:GetName().."EditBox"]:SetFocus();  --/Q
		_G[self:GetName().."EditBox"]:SetText("");  --/Q
	end,
	OnHide = function(self)  --/P
--		if ( ChatFrameEditBox:IsVisible() ) then  /Q
--			ChatFrameEditBox:SetFocus();  /Q
--		end  /Q
		_G[self:GetName().."EditBox"]:SetText("");  --/Q
	end,
	EditBoxOnEnterPressed = function(self)  --/P
		local editBox = _G[self:GetParent():GetName().."EditBox"];  --/Q
		SKG_OnMessageBoxAccept(editBox:GetText());
		self:GetParent():Hide();  --/P
	end,
	EditBoxOnEscapePressed = function(self)
		self:GetParent():Hide();
	end,
	timeout = 0,
	exclusive = 1,
	whileDead = 1
};

StaticPopupDialogs[SKG_CONFIRM] = {
	text = "Placeholder",
	button1 = TEXT(ACCEPT),
	button2 = TEXT(CANCEL),
	hasEditBox = false,
	maxLetters = 16,
	OnAccept = function()
				SKG_OnConfirm(StaticPopupDialogs[SKG_CONFIRM].arg1, 
								StaticPopupDialogs[SKG_CONFIRM].arg2, 
								StaticPopupDialogs[SKG_CONFIRM].arg3);
				end,
	timeout = 0,
	exclusive = 1,
	whileDead = 1
};

SavedRollLists = { };

SKG_Log = { };

SKG_Options = { };

SKG_messageQueue = { };

--------------------------------------------------------------------------------------------------
-- Internal functions
--------------------------------------------------------------------------------------------------

--
-- Uncategorized functions; will sort later
--

-- Sea has this exact function in it, but in the interest
-- of minimizing dependencies, and since this function is so
-- damn simple, here it is again.
function SKG_format(format, ...)
	local ret = format;
	local args = { ... };

	for index, arg in ipairs(args) do
		ret = string.gsub(ret, "<"..index..">", tostring(arg));
	end
	return ret;
end

function SKG_confirmDialog(prompt, handler, arg1, arg2, arg3)
	StaticPopupDialogs[SKG_CONFIRM].text = prompt;
	SKG_OnConfirm = handler;
	StaticPopupDialogs[SKG_CONFIRM].arg1 = arg1;
	StaticPopupDialogs[SKG_CONFIRM].arg2 = arg2;
	StaticPopupDialogs[SKG_CONFIRM].arg3 = arg3;
	StaticPopup_Show(SKG_CONFIRM);
end

function getVersionComponents()
	-- Return mod version as major, minor
	local major = math.floor(SKG_VERSION/100.0);

	return major, (SKG_VERSION-(major * 100));
end

local function SKG_print(msg)
	if( DEFAULT_CHAT_FRAME ) then
		DEFAULT_CHAT_FRAME:AddMessage(msg);
	end
end

function SKG_Debug(msg)
	if (SKG_DEBUG) then
		SKG_print(msg);
	end
end

local function isLeader()
	-- Return true if the player is the raid leader or an assist,
	-- or if a raid is not active, true if the player is the party leader
	if (SKG_DEBUG) then --/A
		return SKG_DEBUG;
	end
	if (GetNumGroupMembers() > 0) then
		return(UnitIsGroupLeader("player") or UnitIsGroupAssistant("player"));
--/S	if (GetNumRaidMembers() > 0) then
--		return (IsRaidLeader() or IsRaidOfficer());
--	elseif (GetNumPartyMembers() > 0) then
--		return IsPartyLeader();
	end
	return false;
end

function SKG_Sticky_Whisper_toggle(action)  --/O
	if (action == "send") then
		SKG_STICKY_WHISPER = ChatTypeInfo.WHISPER.sticky;
		ChatTypeInfo.WHISPER.sticky = 0;
	else
		if (action == "finish" and SKG_STICKY_WHISPER == 1) then
			ChatTypeInfo.WHISPER.sticky = 1;
		end
	end
end

function isInGroup()
	-- Return true if the player is in a party or raid
	if (SKG_DEBUG) then --/A
		return SKG_DEBUG;
	end
	return (GetNumGroupMembers() > 0);
--/S	return ((GetNumPartyMembers() > 0) or (GetNumRaidMembers() > 0));
end

local function getClassColor(class)
	return RAID_CLASS_COLORS[string.upper(class)];
end

local function SKG_sortRolls(rollList)
	-- Make sure all rolls in the supplied list
	-- are in sorted order.
	local sorted = { };

	for idx, rollInfo in ipairs(rollList) do
		table.insert(sorted, rollInfo.roll);
	end
	table.sort(sorted);

	-- We want descending order...
	for idx, rollNumber in ipairs(sorted) do
		rollList[(#sorted) - idx + 1].roll = sorted[idx];
	end
end

local function SKG_prefixizeList(listName)
	if(SKG_Options.prefix and SKG_Options.use_prefix) then
		return SKG_Options.prefix.." - "..listName;
	end
	return listName
end

local function SKG_FrameDropDown_Initialize()
	local info = UIDropDownMenu_CreateInfo();
	local index = 1;
	local selected = nil;

	for saveName, _ in pairs(SavedRollLists) do
		if (index > UIDROPDOWNMENU_MAXBUTTONS) then
			SKG_print(SKG_MSG_TOO_MANY_LISTS);
			break;
		end
		-- Skip this list if we are filtering the drop down
		-- for only lists of the configured prefix.
		if (not SKG_Options.prefix_filter or 
				not SKG_Options.prefix or
				SKG_Options.prefix == string.sub(saveName, 1, 
					string.len(SKG_Options.prefix))) then
			info = { };
			info.text = saveName;
			info.func = SKG_FrameDropDownButton_OnClick;
			UIDropDownMenu_AddButton(info);
			if (saveName == l_loadedList) then
				selected = index;
			end
			index = index + 1;
		end
	end
	SKG_Debug('Selected: '..tostring(selected));
	if (selected) then--/E
		UIDropDownMenu_SetSelectedID(SKG_FrameDropDown, selected);
	end
end

local function SKG_VariablesLoaded()
	local major, minor = getVersionComponents();

	SKG_TitleText:SetText(string.format(SKG_TITLE,
                                               major, minor, 0));

	-- Make sure we are sorted from previous versions that were not.
	for _, rollList in pairs(SavedRollLists) do
		SKG_sortRolls(rollList);
	end

	if ( not SKG_Options.use_prefix ) then
		SKG_Options.use_prefix = false
	end;
	SKG_UsePrefix:SetChecked( SKG_Options.use_prefix );

--/A	if ( not SKG_Options.autoMode ) then
--/A		SKG_Options.autoMode = true
--/A	end;
	SKG_AutoToggle:SetChecked( SKG_Options.autoMode );
	
	AddonChanReg = RegisterAddonMessagePrefix(SKG_CHANNEL_PREFIX);  --/R
	if (AddonChanReg == false or AddonChanReg == nil) then
		SKG_print('Unable to register for Addon Messages');
	end

	if ( not SKG_Options.prefix_filter ) then
		SKG_Options.prefix_filter = false
	end;
	SKG_FilterToggle:SetChecked( SKG_Options.prefix_filter );
   
	if ( not SKG_Options.prefix ) then
		SKG_Options.prefix = ""
	end;
	SKG_PrefixEditBox:SetText( SKG_Options.prefix );

	if ( not SKG_Options.raidWarn ) then
		SKG_Options.raidWarn = false
	end;
	SKG_RaidWarn:SetChecked( SKG_Options.raidWarn );

	if ( not SKG_Options.sendPosition ) then
		SKG_Options.sendPosition = false
	end;
	SKG_SendPosition:SetChecked( SKG_Options.sendPosition );

	if ( not SKG_Options.spamAll ) then
		SKG_Options.spamAll = false
	end;
	SKG_SpamAll:SetChecked( SKG_Options.spamAll );
--/C
	if ( not SKG_Options.minroll ) then
		SKG_Options.minroll = 1
	end;
	SKG_MinRollEditBox:SetText( SKG_Options.minroll);
--/C	
--/H
	if ( SKG_Options.allLists == nil ) then
		SKG_Options.allLists = true
	end;
	SKG_AllLists:SetChecked( SKG_Options.allLists );
--/H
--/I
	if ( SKG_Options.syncGuild == nil ) then
		SKG_Options.syncGuild = false
	end
	SKG_SyncGuild:SetChecked( SKG_Options.syncGuild );

	if ( SKG_Options.syncRaid == nil and SKG_Options.syncGuild == false) then
		SKG_Options.syncRaid = true
	end
	SKG_SyncRaid:SetChecked( SKG_Options.syncRaid );
--/I
	if ( not SKG_Options.xpos ) then
		SKG_Options.xpos = 100
	end;
	
	if ( not SKG_Options.ypos) then
		SKG_Options.ypos = 100
	end;

	SKG_Frame:SetPoint( "TOPLEFT", "UIParent", "BOTTOMLEFT",
						SKG_Options.xpos, SKG_Options.ypos);

	-- don't think we need this anymore
	-- SKG_Net_VariablesLoaded();
	SKG_Options.lastSavedVersion = SKG_VERSION;

   -- Perform any fixups for the roll list for version compatibility.  /A
   for listName, list in pairs(SavedRollLists) do
      for _, rollInfo in ipairs(list) do
         if (not rollInfo.fixupVersion or
             rollInfo.fixupVersion < SKG_VERSION) then
            -- Clear a class name if it's accidentally localized.
            if (rollInfo.class and
                RAID_CLASS_COLORS[string.upper(rollInfo.class)] == nil) then
               rollInfo.class = nil;
            end
            -- Set the fixup version
            rollInfo.fixupVersion = SKG_VERSION
         end
      end
   end

	-- Lets do some more with the sync key. Display it:
	if ( not SKG_SyncKey ) then
		SKG_SyncKey = 0
	end;
	SKG_TitleText:SetText(string.format(SKG_TITLE, major, minor, SKG_SyncKey));
end

local function copyRollList(rollList)
	local ret = { };

	for _, rollInfo in ipairs(rollList) do
		local newRollInfo = { };

		for key, val in pairs(rollInfo) do
			newRollInfo[key] = val;
		end
		table.insert(ret, newRollInfo);
	end
	return ret;
end

local function addUndo(undoToken)
	undoToken.internal_listAsOfUndo = l_currentRollList;
	table.insert(l_undoList, undoToken);
	if (#l_undoList > 32) then
		table.remove(l_undoList, 1);
	end
end

local function updateFrozenPlayers(list)
	local people = { };
	local theList = list;

	if (not theList) then
		theList = l_currentRollList;
	end

	people[UnitName("player")] = 1;

	if (GetNumGroupMembers() > 0) then
		local groupType
		if (IsInRaid()) then
			groupType = "raid"
		else
			groupType = "party"
		end
		for index = 1, GetNumGroupMembers(), 1 do
			local name = UnitName(groupType..index)
			if (name) then
				people[name] = 1;
			end
		end
	end
--/S	if (GetNumRaidMembers() > 0) then
--		for index = 1, 40, 1 do
--			local name = UnitName("raid"..index);
--
--			if (name) then
--				people[name] = 1;
--			end
--		end
--	end
--	if (GetNumPartyMembers() > 0) then
--		for index = 1, 5, 1 do
--			local name = UnitName("party"..index);
--
--			if (name) then
--				people[name] = 1;
--			end
--		end
--	end

	for _, rollInfo in ipairs(theList) do
--/A		if (people[rollInfo.player] == nil and
		if (people[string.sub(rollInfo.player,3)] == nil and
				not l_reserveList[rollInfo.player]) then
			rollInfo.frozen = 1;
		else
			rollInfo.frozen = 0;
		end
	end
end

local function checkSupervisor()
	if (isInGroup() and 
			not l_supervisor) then
		SKG_print(SKG_MASTER_NEEDED);
		return false;
	end
	return true;
end

local function checkLoadedList()
	if (not l_loadedList) then
		SKG_print(SKG_LIST_NEEDED);
		return false;
	end
	return true;
end

local function getUnitId(player)
	if (GetNumGroupMembers() > 0) then
		local groupType
		if (IsInRaid()) then
			groupType = "raid"
		else
			groupType = "party"
		end
		for index = 1, GetNumGroupMembers(), 1 do
			if (UnitName(groupType..index) == player) then
					return groupType..index;
			end
		end
	end
			
--/S	if (GetNumRaidMembers() > 0) then
--		for index = 1, 40, 1 do
--			if (UnitName("raid"..index) == player) then
--				return "raid"..index;
--			end
--		end
--	end
--	if (GetNumPartyMembers() > 0) then
--		for index = 1, 5, 1 do
--			if (UnitName("party"..index) == player) then
--				return "party"..index;
--			end
--		end
--	end
	if (player == UnitName("player")) then
		return "player";
	end
end

local function checkRaidStatus()
	if (isInGroup()) then
		-- The following code is causing spurious un-masters.
		--      if (l_supervisor and not isLeader()) then
		--         SKG_print(SKG_MSG_NO_LEADER);
		--         l_supervisor = false;
		--         SKG_Net_IAmNotTheMaster(UnitName("player"));
		--      end
--/S		if (l_supervisor and
--				(GetNumPartyMembers() > 1 or GetNumRaidMembers() > 1)) then
		if (l_supervisor) then
			SKG_Net_IAmTheMaster(UnitName("player"));
		end
	else
		SKG_Debug("No longer in a group.");
		l_inRaid = false;
		l_supervisor = false;
		SKG_Net_NotInRaid();
	end
end

local function get_auto_channel()
	-- Return an appropriate channel in order of preference: /raid, /p, /s
	local channel = (IsInRaid() and "RAID" or (GetNumSubgroupMembers() +1) == GetNumGroupMembers() and "PARTY" or "SAY")
--/S	if (GetNumPartyMembers() > 0) then
--	if (GetNumSubgroupMembers() > 0) then
--		channel = "PARTY"
--	end
--/S	if (GetNumRaidMembers() > 0) then
--	if (IsInRaid()) then
--		channel = "RAID"
--	end
	return channel
end

local function chat(msg)
	-- Send a chat message
	local channel = get_auto_channel()
	SendChatMessage(msg, channel)
end

function SKG_tellPlayer(player, msg, ptype, noecho)
	local NESendMsg = "<SKG> "..msg;
	local ESendMsg = "[SKG] "..msg;
	if (ptype == nil) then
		ptype = "NORMAL";
	end
	if (noecho) then
--		SendChatMessage("<SKG> "..msg, "WHISPER", nil, player);
		ChatThrottleLib:SendChatMessage(ptype, "SKG", "<SKG> "..msg, "WHISPER", nil, player);	--/K
	else
--		SendChatMessage("[SKG] "..msg, "WHISPER", nil, player);
		ChatThrottleLib:SendChatMessage(ptype, "SKG", "[SKG] "..msg, "WHISPER", nil, player);	--/K
	end
end

local function loadList(listName)
	l_loadedList = listName;
	UIDropDownMenu_SetText(SKG_FrameDropDown, listName);
	l_currentRollList = SavedRollLists[listName];
	SKG_ListScrollFrame.selected = nil;
	SKG_Update();
end

local function doSetAutoRollList(unitId)
	local rollList = SavedRollLists[SKG_prefixizeList("Raid")];
	local found = false;

	if (rollList == nil) then
		rollList = { };
		SavedRollLists[SKG_prefixizeList("Raid")] = rollList;
	end
   
	for _, rollInfo in ipairs(rollList) do
		if (rollInfo.player == UnitName(unitId)) then
			found = true;
			break
		end
	end

	if (not found) then
		loadList(SKG_prefixizeList("Raid"));
		return true;
	end

	rollList = SavedRollLists[SKG_prefixizeList(UnitClass(unitId))];
	found = false;

	if (rollList == nil) then
		rollList = { };
		SavedRollLists[SKG_prefixizeList(UnitClass(unitId))] = rollList;
	end
   
	for _, rollInfo in ipairs(rollList) do
		if (rollInfo.player == UnitName(unitId)) then
			found = true;
			break
		end
	end

	if (not found) then
		loadList(SKG_prefixizeList(UnitClass(unitId)));
		return true;
	end
	return false;
end

local function setAutoRollList(player)
	local unitId = getUnitId(player);

	if (unitId) then
		return doSetAutoRollList(getUnitId(player));
	end
	return false;
end

local function doNewList(listName)
	if (SavedRollLists[listName]) then
		local undoToken = {
			undo = function(this)
				SavedRollLists[this.listName] = this.savedList;
			end,
			listName = listName,
			savedList = SavedRollLists[listName],
			actionName = SKG_format(SKG_FMT_ACTION_SAVE, listName);
		};
		addUndo(undoToken);
	end

	SavedRollLists[listName] = { };
end

local function itemLinkClicked(linkText)
	if (not checkSupervisor()) then
		return;
	end
   
	if (not checkLoadedList()) then
		return;
	end
   
	SKG_OpenBidding(linkText);
end

-- Global UI functions

function SKG_OnMessageBoxAccept(msg)
	if (not checkSupervisor()) then
		return;
	end

	doNewList(msg);

	if (l_isCaptureActive ~= 0) then
		SKG_CaptureToggle();
	end
	loadList(msg);
	if (SKG_Options.syncGuild or SKG_Options.syncRaid) then  --/I
		SKG_Net_NewList(l_loadedList);
	end
end

function SKG_LootFrameItem_OnClick(itemLink)
	if (
		IsAltKeyDown() 
		and not IsShiftKeyDown()
		and not IsControlKeyDown()
	) then
		itemLinkClicked(itemLink);  --/P
	end  --/A
end

function SKG_New()
	StaticPopup_Show(SKG_POPUP);
end

--function SKG_ChatFrame_OnEvent(event)  /E
function SKG_ChatFrame_OnEvent(window, event, arg1, ...)  --/E
	-- Intercept messages before they make them to the chat frame.
	if (event == "CHAT_MSG_WHISPER_INFORM" and
			(string.sub(arg1, 1, 5) == "<SKG>" or
				string.sub(arg1, 1, 9) == "<<<SKG>>>")) then
		--SKG_Debug(arg1);
		return;
	end

	if (event == "CHAT_MSG_WHISPER" and
			string.sub(arg1, 1, 9) == "<<<SKG>>>") then
		--SKG_Debug(arg1);
		return;
	end

	-- Some mods that hook the chat frame expect all these durn args.
--	SKG_originalChatFrame_OnEvent(event, arg1, arg2, arg3,   /E
--		arg4, arg5, arg6, arg7, arg8, arg9);
	SKG_originalChatFrame_OnEvent(window, event, arg1, ...); --/E
end

function SKG_ChatFrame_OnHyperlinkShow(link, text, button)
	if (IsAltKeyDown() and 
			not IsShiftKeyDown() and 
			not IsControlKeyDown()) then
		itemLinkClicked(text);
	end
end

function SKG_OnLoad(self)  --/P
	local index;
	local value;

	self:RegisterEvent("VARIABLES_LOADED");  --/P
	self:RegisterEvent("CHAT_MSG_SYSTEM");
	self:RegisterEvent("CHAT_MSG_CHANNEL");
	self:RegisterEvent("CHAT_MSG_WHISPER");
	self:RegisterEvent("CHAT_MSG_ADDON");

	if (isInGroup()) then
		checkRaidStatus();
	end

	self:RegisterEvent("RAID_ROSTER_UPDATE");
	self:RegisterEvent("PARTY_MEMBERS_CHANGED");

	-- Register our slash command
	SLASH_SKG1 = "/skg";
	SlashCmdList["SKG"] =
		function(msg)
			SKG_SlashCommandHandler(msg);
		end

	-- Hook the ChatFrame OnEvent function
	SKG_originalChatFrame_OnEvent = ChatFrame_OnEvent;
	ChatFrame_OnEvent = SKG_ChatFrame_OnEvent;

	-- Hook the LootFrameItem_OnClick function
	-- Changed in 2.2 (9/25/07)
	-- hooksecurefunc("LootButton_OnClick",
	-- 				SKG_LootFrameItem_OnClick);
	hooksecurefunc("HandleModifiedItemClick",
					SKG_LootFrameItem_OnClick);

	-- Hook the ChatFrame_OnHyperlinkShow function
	hooksecurefunc("ChatFrame_OnHyperlinkShow",
					SKG_ChatFrame_OnHyperlinkShow);

	-- Set the position to the last saved value if any
	-- Moving this code to the VariablesLoaded event handler thinger
	--   if (SKG_Options.xpos and SKG_Options.ypos) then
	--      SKG_Frame:SetPoint("TOPLEFT","UIParent","BOTTOMLEFT",
	--                                 SKG_Options.xpos,
	--                                 SKG_Options.ypos);
	--   end

	local major, minor = getVersionComponents();

	SKG_print(string.format(SKG_LOADED_MSG, major, minor));
end

local function SKG_ProcessRoll(player, roll)
	local newRoll = { };
	local inserted = false;

	if (SKG_Options.autoMode) then
		if (not setAutoRollList(player)) then
			SKG_print(SKG_format(SKG_FMT_NO_AUTO_ADD, player));
			return
		end
	end
      
	for _, rollInfo in ipairs(l_currentRollList) do
		if (rollInfo.player == player) then
			return
		end
	end
      
	if (l_loadedList) then
		SKG_print(SKG_format(SKG_FMT_ADD, player, l_loadedList));
	else
		SKG_print(SKG_format(SKG_FMT_ADD, player, SKG_MSG_CURRENT_LIST));
	end
   
	local undoToken = { undo =
		function(this)
			table.remove(this.rollList,
			this.rollIndex);
		end,
		rollList = l_currentRollList,
		actionName = SKG_format(SKG_FMT_ACTION_ADD, player)
	};
   
	if (roll and roll ~= 0) then
		newRoll.roll = roll;
	else
		newRoll.roll = 0;
		newRoll.insert_bottom = true;
	end
   
	newRoll.player = player;
	newRoll.frozen = 0;
	local unitId = getUnitId(string.sub(player,3));  --/L
	if (unitId) then newRoll.class = UnitClass(unitId) end --/L
 
	local firstPossibleIndex, lastPossibleIndex;
   
	-- Find the first and last possible positions where we can
	-- insert the roll.  Normally, the first possible position is
	-- before the first person with an equal or lesser roll than us, and
	-- the last possible position is after the last person with 
	-- an equal or greater roll than us.
	-- However, if the roll argument to this function is nil, we will
	-- only be inserted at the bottom, and can only have tie breakers with
	-- other rolls that have been inserted at the bottom since the last suicide.
	for index, rollInfo in ipairs(l_currentRollList) do
		if (newRoll.insert_bottom) then
			if (rollInfo.insert_bottom and not firstPossibleIndex) then
				firstPossibleIndex = index;
				break
			end
		else
--			if (roll == rollInfo.roll and not firstPossibleIndex) then   /B
			if (newRoll.roll == rollInfo.roll) then  --</B
				if (string.sub(newRoll.player,1,1) == "B") then
					newRoll.roll = newRoll.roll - 1;
				elseif (string.sub(newRoll.player,1,1) == "A" and not firstPossibleIndex) then
					firstPossibleIndex = index;
					for bindex = index - 1, 1, -1 do
						newRoll.roll = newRoll.roll + 1;
						--SKG_Debug("Bindex:"..bindex.." Roll:"..newRoll.roll);
						if (newRoll.roll == l_currentRollList[bindex].roll) then
							firstPossibleIndex = bindex;
						else
							break
						end
					end
				end  --/B>
--			elseif (roll > rollInfo.roll) then
			elseif (newRoll.roll > rollInfo.roll) then
				lastPossibleIndex = index;
				break
			end
		end
	end
   
	if (not lastPossibleIndex) then
		lastPossibleIndex = #l_currentRollList + 1;
	end
	if (not firstPossibleIndex) then
		firstPossibleIndex = lastPossibleIndex;
	end
   
	local realIndex = firstPossibleIndex;
   
	if (lastPossibleIndex - firstPossibleIndex > 0) then
		-- Note: possible exploit by having the tie breaker number generated on
		-- the client side.  TODO: use an in-game /random roll.
--		realIndex = math.random(firstPossibleIndex, lastPossibleIndex);  --/B
		SKG_print(SKG_format(SKG_FMT_TIE,
					player,
					(lastPossibleIndex - firstPossibleIndex) + 1,
					realIndex));
		SKG_Debug("Index:"..realIndex.." Roll:"..newRoll.roll)
	end

	table.insert(l_currentRollList, realIndex, newRoll);
	undoToken.rollIndex = realIndex;
	addUndo(undoToken);
	SKG_sortRolls(l_currentRollList);
	SKG_Update();

	SKG_Debug("(2) Processing roll ("..
				newRoll.player.."): "..
				newRoll.roll.." new = "..
				tostring(newRoll.insert_bottom));

	if (l_supervisor) then
		if (SKG_Options.syncGuild or SKG_Options.syncRaid) then  --/I
			SKG_Net_InsertPlayer(l_loadedList, realIndex, newRoll);
		end
	end
end

function SKG_MsgReceived(arg1)
	local msg = arg1
	---- Check to see if it's a /random roll:
--	SKG_Debug("l_isCaptureActive:"..l_isCaptureActive);
	if (l_isCaptureActive == 1) then
		local pattern = SKG_ROLL_PATTERN
		local player, roll, min_roll, max_roll, report
		_, _, player, roll, min_roll, max_roll = string.find(msg, pattern)
--/B		if (player and min_roll == ""..SKG_ROLL_MIN and max_roll == ""..SKG_ROLL_MAX) then 
--/C		if (player and max_roll == ""..SKG_ROLL_MAX and (min_roll == ""..SKG_ROLL_MIN_1 or min_roll == ""..SKG_ROLL_MIN_500)) then  --/B
		if (player and max_roll == ""..SKG_ROLL_MAX and min_roll == ""..SKG_Options.minroll) then  --/B
			if (not checkLoadedList()) then
				return;
			end
--/A			SKG_ProcessRoll(player, tonumber(roll));
			SKG_ProcessRoll("A-"..player, tonumber(roll));
			SKG_ProcessRoll("B-"..player, (2000-tonumber(roll)));  --/A
		end
	end
end

function SKG_OnEvent(self, event, ...)  --/P
	local arg1, arg2, arg3, arg4 = ...  --/P
	if( event == "VARIABLES_LOADED" ) then
		SKG_VariablesLoaded();
	elseif ( event == "CHAT_MSG_SYSTEM" ) then
		SKG_MsgReceived(arg1);  --/P
	elseif (event == "CHAT_MSG_ADDON") then
		if (arg1 == SKG_CHANNEL_PREFIX) then
			SKG_Net_MessageIn(arg4, arg2);
		end
	elseif ( event == "RAID_ROSTER_UPDATE" or
				event == "PARTY_MEMBERS_CHANGED" ) then
		checkRaidStatus();
	elseif ( event == "CHAT_MSG_WHISPER" ) then
		SKG_Debug("CHAT_MSG_WHISPER arg1 = "..arg1);
		if (string.sub(arg1, 1, 9) == "<<<SKG>>>") then
			SKG_Net_OnEvent(...);
		elseif (string.lower(arg1) == "suicide") then
			SKG_Lookup(arg2, true);
--/A		elseif (string.lower(arg1) == "bid") then
--/A			SKG_Bid(arg2);
		elseif (string.lower(string.sub(arg1, 1, 3)) == "bid") then  --/A
		SKG_Bid(arg2, arg1);  --/A
		elseif (string.lower(arg1) == "retract") then
			SKG_RetractBid(arg2);
			end
	end
end

function SKG_ItemButton_OnClick(self, button)
	if( button == "LeftButton" ) then
		SKG_ListScrollFrame.selected = self:GetID() + FauxScrollFrame_GetOffset(SKG_ListScrollFrame);
		--SKG_Debug("selection set to: "..SKG_ListScrollFrame.selected);
		SKG_Update();
	end
end

function SKG_OnShow()
	PlaySound("igMainMenuOpen");
	SKG_Update();
end

function SKG_OnHide()
	PlaySound("igMainMenuClose");
end

function SKG_FrameDropDown_OnLoad()
	local index = 1;
	UIDropDownMenu_Initialize(SKG_FrameDropDown, SKG_FrameDropDown_Initialize);
	UIDropDownMenu_SetWidth(SKG_FrameDropDown,80);
	UIDropDownMenu_SetButtonWidth(SKG_FrameDropDown,24);
	UIDropDownMenu_JustifyText(SKG_FrameDropDown, "LEFT")
end

function SKG_FrameDropDownButton_OnClick(self)  --/p
	local oldID = UIDropDownMenu_GetSelectedID(SKG_FrameDropDown);

	if (l_isCaptureActive ~= 0 and not SKG_Options.autoMode) then
		SKG_CaptureToggle();
	end
	--SKG_Debug("Old ID: "..tostring(oldID).."Selected ID: "..tostring(self:GetID()));
	UIDropDownMenu_SetSelectedID(SKG_FrameDropDown, self:GetID());  --/p
	if( oldID ~= self:GetID() ) then
		--SKG_Debug("List name: "..self:GetText());
		loadList(self:GetText());  --/p
	end
end

function SKG_Refresh()
	FauxScrollFrame_SetOffset(SKG_ListScrollFrame, 0);
	getglobal("SKG_ListScrollFrameScrollBar"):SetValue(0);
	SKG_Update();
end

function SKG_AmITheMaster()
	return l_supervisor;
end

function SKG_OpenBidding(lootLink)
	if (not checkSupervisor()) then
		return
	end

	if (l_biddingOpen) then 
		SKG_print(SKG_MSG_BID_OPEN);
		return
	end

	chat(SKG_format(SKG_FMT_BID_NOW_OPEN, lootLink));
	if (SKG_Options.raidWarn and
			isLeader()) then
		SendChatMessage(SKG_format(SKG_RAID_WARN_TEXT, lootLink),
							"RAID_WARNING");
	end
	l_biddingOpen = true;
	l_highBidders = {};
	l_currentLootLink = lootLink;  --/P
end

function SKG_CloseBidding()
	if (not checkSupervisor()) then
		return
	end

	if (not l_biddingOpen) then
		SKG_print(SKG_MSG_NOTOPEN);
		return
	end

	chat(SKG_format(SKG_MSG_BID_CLOSED, l_currentLootLink));  --/P
	if (#l_highBidders == 0) then
		chat(SKG_MSG_NO_BIDDERS);
	else
		chat(SKG_format(SKG_FMT_WINNER, l_highBidders[1]));
		SKG_SuicideByName(l_highBidders[1]);
	end
	l_biddingOpen = false;
end

--/A local function enterBid(player)
local function enterBid(player, arg1) --/A
	local index = 1;
	local rollIndex, rollInfo;

	SKG_Debug("enterBid arg1= "..arg1);
	SKG_Sticky_Whisper_toggle("send");	--/O
	for rollIndex, rollInfo in ipairs(l_currentRollList) do
--/A		if (l_highBidders[index] == player) then
		if (l_highBidders[index] == (string.upper(string.sub(arg1, 5, 5)).."-"..player)) then  --/O
			if (index == 1) then
				SKG_tellPlayer(player, SKG_MSG_ALREADY_BIDDER, "NORMAL");
			else
				SKG_tellPlayer(player, SKG_format(SKG_FMT_NOT_HIGH_BIDDER, l_highBidders[1]), "NORMAL");
			end
			SKG_Sticky_Whisper_toggle("finish");	--/O
			return
		end
--/A		if (rollInfo.player == player) then
--/A			table.insert(l_highBidders, index, player);
		if (rollInfo.player == string.upper(string.sub(arg1, 5, 5)).."-"..player) then  --/O
			table.insert(l_highBidders, index, rollInfo.player);  --/A

			if (index == 1) then
				chat(SKG_format(SKG_FMT_NEW_HIGH_BIDDER, player, rollIndex));
			else
				SKG_tellPlayer(player, SKG_format(SKG_FMT_NOT_HIGH_BIDDER, l_highBidders[1]), "NORMAL");
			end
			SKG_Sticky_Whisper_toggle("finish");	--/O
			return
		end
		if (rollInfo.player == l_highBidders[index]) then
			index = index + 1;
		end
	end
	if (string.upper(arg1) == "A" or string.upper(arg1) == "B") then  --/K
		SKG_tellPlayer(player, SKG_MSG_NOT_ON_LIST, "NORMAL");
	else
		SKG_tellPlayer(player, SKG_MSG_BID_INCORRECT, "NORMAL");
	end
	SKG_Sticky_Whisper_toggle("finish");	--/O
end

function SKG_RetractBid(player)
	SKG_Sticky_Whisper_toggle("send");	--/O
	for index, bidder in ipairs(l_highBidders) do
--/A		if (bidder == player) then
		if (string.sub(bidder, 3) == player) then  --/A
			table.remove(l_highBidders, index);
			if (index == 1 and #l_highBidders ~= 0) then
				local myIndex, rollInfo;

				for myIndex, rollInfo in ipairs(l_currentRollList) do
					if (rollInfo.player == l_highBidders[1]) then
						chat(SKG_format(SKG_FMT_NEW_HIGH_BIDDER, string.sub(l_highBidders[1], 3), 
											myIndex));  --/M
						break
					end
				end
			end
			SKG_tellPlayer(player, SKG_MSG_BID_RETRACT, "NORMAL");
			if (#l_highBidders == 0) then
				chat(SKG_MSG_BID_ALL_RETRACT);
			end
			SKG_Sticky_Whisper_toggle("finish");	--/O
			return
		end
	end
	SKG_tellPlayer(player, SKG_MSG_BID_NO_RETRACT, "NORMAL");
end
      
--/A function SKG_Bid(player)
function SKG_Bid(player, arg1)  --/A
	if (not l_supervisor) then
		SKG_print(SKG_MSG_NOT_MASTER);
		return;
	end

	SKG_Sticky_Whisper_toggle("send");	--/O
	if (not l_biddingOpen) then
		SKG_tellPlayer(player, SKG_MSG_NOTOPEN, "NORMAL", true);
	elseif (not l_loadedList) then
		SKG_tellPlayer(player, SKG_MSG_NO_LIST_SELECTED, "NORMAL");
	else
--/A		enterBid(player);
		enterBid(player, arg1); --/A
	end
	SKG_Sticky_Whisper_toggle("finish");	--/O
end

--------------------------------------------------------------------------------------------------
-- Callback functions
--------------------------------------------------------------------------------------------------
function SKG_CaptureToggle()
	if(l_isCaptureActive == 1) then
		l_isCaptureActive = 0;
		SKG_FrameCaptureButton:SetText(SKG_INACTIVE_LABEL);
	elseif (checkSupervisor()) then
		l_isCaptureActive = 1;
		SKG_FrameCaptureButton:SetText(SKG_ACTIVE_LABEL);
	end
end

function SKG_ModeToggle(autoMode)
	if ( autoMode ) then
		SKG_Options.autoMode = true;
	else
		SKG_Options.autoMode = false;
	end
end

function ToggleSKG()
	if ( SKG_Frame:IsVisible() ) then
		if ( l_isCaptureActive ) then
			SKG_CaptureToggle();
		end
		SKG_Frame:Hide();
	else
		SKG_Frame:Show();
	end
end

function SKG_SlashCommandHandler(msg)
	local first, max, player, roll;

	if (not msg or msg == "") then
		ToggleSKG();
	else
		local lowerMsg = string.lower(msg);
		if (lowerMsg == SKG_SPAM_CMD) then
			SKG_SpamChat();
			return
		elseif (lowerMsg == SKG_NOROLL_CMD) then
			SKG_SpamNotRolled();
			return
		elseif (lowerMsg == SKG_HELP_CMD) then
			SKG_print(SKG_HELP);
			return
		elseif (string.sub(lowerMsg, 1, string.len(SKG_SYNC_CMD)) == 
					SKG_SYNC_CMD) then
			local args = strtrim(string.sub(msg, string.len(SKG_SYNC_CMD) + 1));
			
			if (args == "" and checkSupervisor()) then
				SKG_Net_StartFullSync();
			else
				--SKG_Debug("args = "..args);
				-- Form /sk sync <player>
				local arglist = { strsplit(" ", args) };

				if (#arglist == 1) then
					--SKG_Debug("Synching to '"..arglist[1].."'");
					SKG_Net_StartFullSync(arglist[1]);
				else
					-- Form /sk sync <player> <list1> [list2, ...]
					-- Only sends specific lists
					local playerName = arglist[1];
					--SKG_Debug("Synching to player '"..arglist[1].."'");
					
					args = strtrim(string.sub(args, string.len(playerName) + 1));  --/I
					--SKG_Debug("args without playerName = "..args);
					--table.remove(arglist, 1);
					arglist = { strtrim(strsplit(",", args)) };  --/I
					SKG_Net_StartFullSync(playerName, arglist);
				end
			end
			return;
		elseif (lowerMsg == SKG_MASTER_CMD) then
			if (not isInGroup()) then
				SKG_print(SKG_MSG_RAID_REQUIRED);
			elseif (isLeader()) then
				SKG_print(SKG_NEW_MASTER);
				l_supervisor = true;
				SKG_Net_Enable(true);
				SKG_Net_IAmTheMaster(UnitName("player"));
			else
				SKG_print(SKG_OFFICER_NEEDED);
			end
			return
		elseif (lowerMsg == SKG_CLOSE_CMD) then
			SKG_CloseBidding();
			return;
		end

		_, _, first, max = string.find(lowerMsg, SKG_SPAM_CMD_PATTERN1);
		if (first) then
			first = tonumber(first);
			max = tonumber(max);
			if (first ~= nil and max ~= nil) then
				SKG_SpamChat(first, max);
				return
			end
			_, _, max = string.find(lowerMsg, SKG_SPAM_CMD_PATTERN2);
			max = tonumber(max);
			if (max ~= nil) then
				SKG_SpamChat(1, max);
				return
			end
		end

		_, _, player, roll = string.find(lowerMsg, SKG_INSERT_CMD_PATTERN);
		if (player and roll) then
			SKG_Debug("Message: "..lowerMsg.." - player: "..player.." - roll: "..roll);
			if (not checkSupervisor() or
					not checkLoadedList()) then
				return;
			end

			player = string.upper(string.sub(player, 1, 1))..string.lower(string.sub(player, 2))
--/A			SKG_ProcessRoll(player, tonumber(roll));
			SKG_ProcessRoll("A-"..player, tonumber(roll));  --/A
			SKG_ProcessRoll("B-"..player, (2000-tonumber(roll)));  --/A
			return
		end

		_, _, player = string.find(lowerMsg, SKG_INSERT_CMD_PATTERN2);
		if (player) then
			SKG_Debug("Message: "..lowerMsg.." - player: "..player);
			if (not checkSupervisor() or
					not checkLoadedList()) then
				return;
			end

			player = string.upper(string.sub(player, 1, 1))..string.lower(string.sub(player, 2));
--/A			SKG_ProcessRoll(player);
			SKG_ProcessRoll("A-"..player);  --/A
			SKG_ProcessRoll("B-"..player);  --/A
			return
		end

		-- necrotic 1.19a
		_, _, player = string.find(lowerMsg, SKG_SEARCH_CMD);
		if (player) then
			SKG_Lookup(player);
			return
		end

		_, _, player = string.find(lowerMsg, SKG_SUICIDE_CMD_PATTERN);
		if (player)then
			if (not checkSupervisor() or
					not checkLoadedList()) then
				return;
			end
			SKG_SuicideByName(player);
			return
		end

		_, _, listName = string.find(msg, SKG_LIST_CMD_PATTERN);
		if (listName) then
			if (SavedRollLists[listName]) then
				loadList(listName);
				SKG_print(SKG_format(SKG_FMT_LIST_SELECTED, listName));
			else
				SKG_print(SKG_format(SKG_FMT_LIST_NOT_FOUND, listName));
			end
			return;
		end

		-- If all else fails RTFM.
		SKG_print(SKG_HELP);
	end
end

function SKG_Update()
	local iItem;
   
	FauxScrollFrame_Update(SKG_ListScrollFrame, #l_currentRollList, 
                          SKG_ITEMS_SHOWN, SKG_ITEM_HEIGHT);
	--SKG_Debug("Scroll frame at "..FauxScrollFrame_GetOffset(SKG_ListScrollFrame));
	-- Show non-present players as blue
	updateFrozenPlayers();
	for iItem = 1, SKG_ITEMS_SHOWN, 1 do
		local itemIndex = iItem + FauxScrollFrame_GetOffset(SKG_ListScrollFrame);
		local item = getglobal("SKG_Item"..iItem);
		local buttonText = getglobal("SKG_Item"..iItem.."Text"); --/E
		
		if( itemIndex <= #l_currentRollList ) then
			item:Show();

			local itemText = itemIndex..". "..l_currentRollList[itemIndex].player;
         
			if (l_currentRollList[itemIndex].roll ~= 0) then
				itemText = itemText.." ("..l_currentRollList[itemIndex].roll..")";
			end

			if (l_currentRollList[itemIndex].insert_bottom) then
				itemText = itemText.." ("..SKG_NEW_INSERT..")";
			end

--/E			item:SetText(itemText);
			buttonText:SetText(itemText);

			if (itemIndex == SKG_ListScrollFrame.selected) then
				if (l_currentRollList[itemIndex].frozen == 0) then
					buttonText:SetTextColor(0.8, 0, 0);
				elseif (l_reserveList[l_currentRollList[itemIndex].player]) then
					buttonText:SetTextColor(0.5, 0.0, 0.0);
				else
					buttonText:SetTextColor(0.8, 0.8, 0);
				end
			else
				local classColor = { r=0.8, g=0.8, b=0.8};

				if (l_currentRollList[itemIndex].class) then
					classColor = getClassColor(l_currentRollList[itemIndex].class);
				else
--/A					local unitId = getUnitId(l_currentRollList[itemIndex].player);
					local unitId = getUnitId(string.sub(l_currentRollList[itemIndex].player,3));  --/A

					if (unitId) then
						_, l_currentRollList[itemIndex].class = UnitClass(unitId);
						classColor = getClassColor(l_currentRollList[itemIndex].class);
					end
				end

				if (l_currentRollList[itemIndex].frozen == 0) then
					if (classColor) then
						buttonText:SetTextColor(classColor.r, classColor.g, classColor.b);
					else
						buttonText:SetTextColor(0.8, 0.8, 0.8);
					end
				elseif (l_reserveList[l_currentRollList[itemIndex].player]) then
					if (classColor) then
						buttonText:SetTextColor(classColor.r * 0.7, 
											classColor.g * 0.7,
											classColor.b * 0.7);
					else
						buttonText:SetTextColor(0.5, 0.5, 0.5);
					end
				else
--					SKG_Debug("Text "..buttonText:GetText());
					buttonText:SetTextColor(0, 0.5, 0.5);
				end
			end
		else
			item:Hide();
		end
	end
end

local function doUndo(fromUi)
	local message = nil;

	if (#l_undoList > 0) then
		local undoToken = l_undoList[#l_undoList];
		undoToken:undo();
		table.remove(l_undoList);
		if(undoToken.actionName) then
			if (fromUi) then
				SKG_print(SKG_UNDID_ACTION..undoToken.actionName);
			end
		end

		SKG_sortRolls(l_currentRollList);
		SKG_Update();

		if (fromUi) then
			if (undoToken.internal_listAsOfUndo) then
				for listName, rollList in pairs(SavedRollLists) do
					if (rollList == undoToken.internal_listAsOfUndo) then
						if (rollList ~= l_currentRollList) then
							loadList(listName);
							return
						end
						break
					end
				end
			end
		end
	elseif (fromUi) then
		SKG_print(SKG_NO_UNDO);
	end

	return message;
end

function SKG_Undo()
	if (not checkSupervisor()) then
		return
	end

	doUndo(true);
	if (SKG_Options.syncGuild or SKG_Options.syncRaid) then  --/I
		SKG_Net_Undo();
	end
end

local function doRemovePlayer(listName, index)
	local undoToken = { undo =
		function(this)
			table.insert(this.rollList, 
			this.rollIndex, 
			this.rollInfo);
		end,
		rollList = SavedRollLists[listName],
		rollInfo = SavedRollLists[listName][index],
		rollIndex = index,
		actionName = SKG_ACTION_REMOVE
	};
	local theList = SavedRollLists[listName];

	addUndo(undoToken);
	table.remove(theList, index);
	SKG_ListScrollFrame.selected = nil;
	SKG_sortRolls(theList);
	SKG_Update();
end

function SKG_Remove()
	if (not checkSupervisor()) then
		return
	end

	if (SKG_ListScrollFrame.selected ~= nil) then
		local index = SKG_ListScrollFrame.selected
		doRemovePlayer(l_loadedList, SKG_ListScrollFrame.selected);
		if (SKG_Options.syncGuild or SKG_Options.syncRaid) then  --/I
			SKG_Net_RemovePlayer(l_loadedList, 
								index);
		end
	end
end

function SKG_Freeze()
	updateFrozenPlayers();
	SKG_Update();
end

function SKG_Insert()
	if (not checkSupervisor()) then
		return;
	end

	if (not checkLoadedList()) then
		return;
	end

	if (UnitIsPlayer("target")) then
--/A		SKG_ProcessRoll(UnitName("target"));
		SKG_ProcessRoll("B-"..UnitName("target"));  --/A
		SKG_ProcessRoll("A-"..UnitName("target"));  --/A
		if(SKG_Options.autoMode) then
			-- Insert twice in auto mode for class and raid list.
			SKG_ProcessRoll(UnitName("target"));
		end
		SKG_ListScrollFrame.selected = nil;
		SKG_Update();
	end
end

local function findNextUnfrozen(index, suicide, list)
	if (index > #list) then
		return suicide
	end

	for iItem = index, #list, 1 do
		if (list[iItem].frozen == 0) then
			return list[iItem];
		end
	end
	return suicide;
end

local function doSuicide(suicide, listName)
	local theList;

	if (not listName) then
		listName = l_loadedList;
		theList = l_currentRollList;
	else
		theList = SavedRollLists[listName];
		if (not theList) then
			SKG_print("Internal error: suiciding non existant list: "..listName);
			return;
		end
	end

	local movingPlayers = { };

	-- Suicide the player represented by the
	-- supplied index into theList
	SKG_print(SKG_format(SKG_SUICIDE, theList[suicide].player));
	updateFrozenPlayers(theList);
	if (theList[suicide].frozen ~= 0 and
			not l_reserveList[theList[suicide].player]) then
		SKG_print(SKG_MSG_NO_SUICIDE_FROZEN);
	else
		local undoToken = { undo =
			function(this)
				for idx=1, #this.rollList, 1 do
					this.rollList[idx] = nil;
				end
				for _, rollInfo in ipairs(this.oldList) do
					table.insert(this.rollList, rollInfo);
				end
			end,
			rollList = theList,
			oldList = copyRollList(theList),
			actionName = SKG_format(SKG_FMT_ACTION_SUICIDE,
							theList[suicide].player) };
      
		addUndo(undoToken);

		local suicideInfo = theList[suicide];

		for iItem = 1, #theList, 1 do
			theList[iItem].insert_bottom = nil;
         
			if (iItem >= suicide and
					(theList[iItem].frozen == 0 or
					l_reserveList[theList.player])) then
				table.insert(movingPlayers, iItem);
				theList[iItem] = findNextUnfrozen(iItem + 1, suicideInfo, theList);
			end
		end
	end
	SKG_sortRolls(theList);
	SKG_Update();

	if (l_supervisor) then
		if (SKG_Options.syncGuild or SKG_Options.syncRaid) then  --/I
			SKG_Net_Suicide(listName, suicide);
		-- I think we need to do this all the time now...
		--SKG_Net_VerboseSuicide(listName, movingPlayers); --/D
		end
	end
end

function SKG_SuicideByName(playerName)
	if (not checkSupervisor()) then
		return;
	end

	for idx, rollInfo in ipairs(l_currentRollList) do
		if (string.lower(playerName) == string.lower(rollInfo.player)) then
			doSuicide(idx);
			return
		end
	end
	SKG_print(SKG_format(SKG_FMT_PLAYER_NOT_FOUND, playerName));
end

function SKG_Suicide()
	if (not checkSupervisor()) then
		return;
	end

	if (SKG_ListScrollFrame.selected ~= nil) then
		doSuicide(SKG_ListScrollFrame.selected);
	end
end

function SKG_Delete()
	if (not checkSupervisor()) then
		return;
	end

	if (l_loadedList ~= nil) then
		SKG_confirmDialog(SKG_MSG_DELETE_CONFIRM, SKG_DoDelete);
	end
end

local function doDeleteList(listName)
   local undoToken = {
      undo = function(this)
                SavedRollLists[this.listName] = this.savedList;
             end,
      listName = listName,
      savedList = SavedRollLists[listName],
      actionName = SKG_format(SKG_FMT_ACTION_DELETE, listName);
   };

   addUndo(undoToken);
   SavedRollLists[listName] = nil;

   if (l_loadedList == listName) then
		l_currentRollList = { };
		l_loadedList = nil;
		SKG_ListScrollFrame.selected = nil;
		UIDropDownMenu_SetSelectedID(SKG_FrameDropDown, 0);
		UIDropDownMenu_SetText(SKG_FrameDropDown, "");  --/G
	end

	if (l_isCaptureActive ~= 0) then
		SKG_CaptureToggle();
	end

	SKG_Update();
end

function SKG_DoDelete()
	if (not checkSupervisor()) then
		return;
	end

	if (l_loadedList ~= nil) then
		local listName = l_loadedList;

		doDeleteList(l_loadedList);
		if (SKG_Options.syncGuild or SKG_Options.syncRaid) then  --/I
			SKG_Net_DeleteList(listName);
		end
	end
end

local function getPlayerTable()
	local allPlayers = { };
	local index, playerName;

	allPlayers[UnitName("player")] = "player";
	if (GetNumGroupMembers(LE_PARTY_CATEGORY_HOME) > 0) then
		local groupType
		if (IsInRaid()) then
			groupType = "raid"
		else
			groupType = "party"
		end
		for index=1, GetNumGroupMembers(LE_PARTY_CATEGORY_HOME), 1 do
			playerName = UnitName(groupType..index)
			if (playerName) then
				allPlayers[playerName] = groupType..index;
			end
		end
	end
			
--/S	if (GetNumPartyMembers() > 0) then
--		for index=1, 5, 1 do
--			playerName = UnitName("party"..index)
--			if (playerName) then
--				allPlayers[playerName] = "party"..index;
--			end
--		end
--	end
--	if (GetNumRaidMembers() > 0) then
--		for index=1, 40, 1 do
--			playerName = UnitName("raid"..index)
--			if (playerName) then
--				allPlayers[playerName] = "raid"..index;
--			end
--		end
--	end
	return allPlayers;
end

local function getAllPlayersInGroup()
	local allPlayers = getPlayerTable();
	local ret = { };

	for playerName, _ in pairs(allPlayers) do
		table.insert(ret, playerName);
	end
	return ret;
end

local function getPlayersRolled(rollListName, playerMap)
	local myRollList;

	if(not playerMap) then
		playerMap = { };
	end

	if (rollListName) then
		myRollList = SavedRollLists[rollListName];
		if(not myRollList) then
			return playerMap;
		end
	else
		myRollList = l_currentRollList;
	end

	for _, rollInfo in ipairs(myRollList) do
		if (not playerMap[rollInfo.player]) then
			playerMap[rollInfo.player] = 1;
		else
			playerMap[rollInfo.player] = playerMap[rollInfo.player] + 1;
		end
	end
	return playerMap;
end

function SKG_SpamNotRolled()
	-- Spam to chat players that have not rolled yet.
	local stillNeedRolls = false;
	local alreadyRolled = { };
	local autoLists = { };
	local allPlayers, playerName, listName;

	for idx, listName in ipairs(SKG_AUTO_LISTS) do
		table.insert(autoLists, SKG_prefixizeList(listName));
	end

	if(SKG_Options.autoMode) then
		allPlayers = getAllPlayersInGroup();

		for _, listName in ipairs(autoLists) do
			alreadyRolled = getPlayersRolled(listName, alreadyRolled);
		end

		for _, playerName in ipairs(allPlayers) do
			if (not alreadyRolled[playerName]) then
				chat(playerName..SKG_MSG_ROLL_TWICE);
				stillNeedRolls = true;
			elseif (alreadyRolled[playerName] == 1) then
				chat(playerName..SKG_MSG_ROLL_ONCE);
				stillNeedRolls = true;
			end
		end
	else
		alreadyRolled = getPlayersRolled();
		allPlayers = getAllPlayersInGroup();
      
		for _, playerName in ipairs(allPlayers) do
--/A			if (not alreadyRolled[playerName]) then
			if (not alreadyRolled["B-"..playerName]) then  --/A
--				chat(playerName..SKG_MSG_STILL_ROLL);
				chat(playerName..SKG_format(SKG_MSG_STILL_ROLL, SKG_Options.minroll));
				stillNeedRolls = true;
			end
		end
	end

	if (not stillNeedRolls) then
		SKG_print(SKG_MSG_EVERYONE_ROLLED);
	end
end

function SKG_SpamChat(first, max)
	if (not first or first < 1) then
		first = 1;
	end
	if (not max) then
		if SKG_Options.spamAll then  -- RPR G1209
			max = 50; --/G
		else
			max = 10;
		end
	end

	local allPlayers = getPlayerTable();
	local index = first;
	local display = { };

	while (index <= #l_currentRollList) do
--/A		if (allPlayers[l_currentRollList[index].player]) then
		if (allPlayers[string.sub(l_currentRollList[index].player,3)]) then  --/A
			table.insert(display, { index=index, player=l_currentRollList[index].player });
		end
		if (#display >= max) then
			break
		end
		index = index + 1;
	end
   
	if(#display < 1) then
		SKG_print(SKG_MSG_NO_NAMES);
		return
	end

	if (first == 1) then
		if (l_loadedList ~= nil) then
			chat(SKG_format(SKG_FMT_TOP_LIST, #display, l_loadedList));
		else
			chat(SKG_format(SKG_FMT_TOP, #display));
		end
	else
		if (l_loadedList ~= nil) then
			chat(SKG_format(SKG_FMT_INDEX_LIST, first,  first+ #display -1, l_loadedList));
		else
			chat(SKG_format(SKG_FMT_INDEX, first, first+ #display - 1));
		end
	end

	local rollInfo;

	for _, rollInfo in ipairs(display) do
		chat(rollInfo.index..": "..
				rollInfo.player.." ("..
--/A				UnitClass(allPlayers[rollInfo.player])..")");
				UnitClass(allPlayers[string.sub(rollInfo.player,3)])..")");  --/A
	end
	-- RPR G1209
	if SKG_Options.sendPosition then
		for i, rollInfo in ipairs(display) do
--/A			tellPlayer(rollInfo.player,
--/A						SKG_SEND_POSITION_MESSAGE1..rollInfo.index..SKG_SEND_POSITION_MESSAGE2 ,
--			tellPlayer(string.sub(rollInfo.player,3),  --/A
--						SKG_SEND_POSITION_MESSAGE1..string.sub(rollInfo.player, 1, 1)..SKG_SEND_POSITION_MESSAGE2..rollInfo.index..SKG_SEND_POSITION_MESSAGE3,  --/A
--						"BULK", true);  -- The final "true" keeps it from echoing to you.
			SKG_queueTell(SKG_SEND_POSITION_MESSAGE1..string.sub(rollInfo.player, 1, 1)..SKG_SEND_POSITION_MESSAGE2..rollInfo.index..SKG_SEND_POSITION_MESSAGE3,
							string.sub(rollInfo.player,3),
							"BULK",
							true);
		end
	end
end

function SKG_Reserve()
	if (not checkSupervisor()) then
		return;
	end

	if (SKG_ListScrollFrame.selected ~= nil) then
		local rollInfo = l_currentRollList[SKG_ListScrollFrame.selected];
      
		if (l_reserveList[rollInfo.player]) then
			SKG_print(SKG_format(SKG_FMT_UNRESERVE, rollInfo.player));
			l_reserveList[rollInfo.player] = nil;
		else
			SKG_print(SKG_format(SKG_FMT_RESERVE, rollInfo.player));
			l_reserveList[rollInfo.player] = true;
		end
		SKG_Update();
		if (SKG_Options.syncGuild or SKG_Options.syncRaid) then  --/I
			SKG_Net_Reserve(rollInfo.player, l_reserveList[rollInfo.player]);
		end
	end
end

-- necrotic
-- additions for a higher order

-- *** update to SKIP FROZENS?	 ***
local function doMoveImpl(list, idx, places, fromUi)
	if (#list > 1) then
		local thisGuy = list[idx];

		if ((idx + places) >= 1 and (idx + places) <= #list) then
			local undoToken = {	undo =
				function(this)
					table.remove(this.rollList, this.firstIndex);
					table.insert(this.rollList, this.secondIndex, this.rollInfo);
					this.rollInfo.insert_bottom = this.wasAtBottom;
					SKG_ListScrollFrame.selected = nil;
				end,
				rollList = list,
				firstIndex = idx + places,
				secondIndex = idx,
				rollInfo = thisGuy,
				wasAtBottom = thisGuy.insert_bottom
			};
         
			if (places > 0) then
				undoToken.actionName = SKG_ACTION_MOVE_DOWN;
			else
				undoToken.actionName = SKG_ACTION_MOVE_UP;
			end
			addUndo(undoToken);
         
			table.remove(list, idx)
			table.insert(list, idx + places, thisGuy)
         
			-- Check whether this guy is being moved above the group
			-- of players marked as newly-inserted at the bottom.  If so,
			-- remove his "bottom" status

			if (#list >= (idx+places+1) and 
					not list[idx+places+1].insert_bottom) then
				-- thisGuy is out of the group of bottom-inserted people.
				thisGuy.insert_bottom = nil;
			elseif ((idx + places) > 1 and list[idx + places - 1].insert_bottom) then
				-- thisGuy is into the group of bottom-inserted people.
				thisGuy.insert_bottom = true;
			end
         
			if (fromUi) then
				SKG_ListScrollFrame.selected = idx + places;
			end
			SKG_sortRolls(list)
			SKG_Update();
			return idx + places;
		end
	end
end

local function doMove(places)
	if (not checkSupervisor()) then
		return;
	end

	local selected = SKG_ListScrollFrame.selected;

	if (SKG_ListScrollFrame.selected ~= nil) then
		doMoveImpl(l_currentRollList, SKG_ListScrollFrame.selected,
					places, true);
		if (SKG_Options.syncGuild or SKG_Options.syncRaid) then  --/I
			SKG_Net_MovePlayer(l_loadedList,
					selected, selected + places);
		end
	end
end

function SKG_MoveUp()
	if (not checkSupervisor()) then
		return;
	end

	doMove(-1);
end

function SKG_MoveDown()
	if (not checkSupervisor()) then
		return;
	end
	doMove(1);
end

function SKG_Lookup(lookupName, whisper)	
	local listName, rollList, idx, rollInfo
	local numMatch =0
	SKG_Debug("lookupName="..lookupName);
	SKG_Sticky_Whisper_toggle("send");	--/O
	for listName, rollList in pairs(SavedRollLists) do
		--SKG_Debug("listName: "..listName)
		for idx, rollInfo in ipairs(rollList) do
		if (SKG_Options.allLists == false and rollList ~= l_currentRollList) then  --/H
			break;
		end
		--/A			if (string.lower(rollInfo.player) == string.lower(lookupName)) then
		if (string.lower(string.sub(rollInfo.player,3)) == string.lower(lookupName)) then  --/A
				local message = rollInfo.player.." "..idx..": "..listName;
				if(whisper) then
--/A					tellPlayer(rollInfo.player, message, true);
					SKG_tellPlayer(string.sub(rollInfo.player,3), message,"NORMAL", true);  --/A
				else
					SKG_print(message);
				end
				numMatch=numMatch+1
			end
		end
	end
	if (numMatch==0) then
		if(whisper) then
			SKG_tellPlayer(lookupName, SKG_format(SKG_FMT_PLAYER_NOT_FOUND, 
						lookupName),"NORMAL", true);
		else
			SKG_print(SKG_format(SKG_FMT_PLAYER_NOT_FOUND, lookupName));
		end
	end

	if (whisper) then
		SKG_print(SKG_format(SKG_FMT_PLAYER_INFORMED, lookupName));
	end
	SKG_Sticky_Whisper_toggle("finish");	--/O
end

-- Only show one of the two frames below at once. If one is visible when they've
-- clicked for the other one, hide it.
function SKG_OFrame()
	if ( SKG_OptionsFrame:IsVisible() ) then
		SKG_OptionsFrame:Hide();
	else
		SKG_FunctionsFrame:Hide();
		SKG_OptionsFrame:Show();
	end
end

function SKG_FFrame()
	if ( SKG_FunctionsFrame:IsVisible() ) then
		SKG_FunctionsFrame:Hide();
		SKG_OptionsFrame:Show();
		SKG_FunctionsButton:SetText(SKG_HIDE_LABEL);
	else
		if ( SKG_OptionsFrame:IsVisible() ) then
			SKG_OptionsFrame:Hide();
			SKG_FunctionsButton:SetText(SKG_FUNCTIONS_LABEL);
		else
			SKG_FunctionsFrame:Show();
			SKG_FunctionsButton:SetText(SKG_OPTIONS_LABEL);
		end
	end
end

function SKG_SetPrefix(prefix)
	if (string.gsub(prefix, "%s", "") == "") then
		-- All white space means no prefix.
		prefix = nil;
	end
	SKG_Options.prefix = prefix;
	SKG_Update();
end

--/C
function SKG_SetMinRoll(minroll)
	if (not tonumber(minroll) or (tonumber(minroll) < 1 and tonumber(minroll) > SKG_ROLL_MAX)) then
		minroll = 1;
	end;
	SKG_Options.minroll = minroll;
	SKG_Update();
end  --/C

function SKG_PrefixFilterToggle(filterOn)
	SKG_Options.prefix_filter = filterOn;
	SKG_Update();
end

function SKG_UsePrefixToggle(usePrefix)
	SKG_Options.use_prefix = usePrefix;
end

function SKG_OnMouseDown(self, arg1)  --/P
	if arg1=="LeftButton" then
		self:StartMoving()
	end
end

function SKG_OnMouseUp(self, arg1)  --/P
	if arg1=="LeftButton" then
		self:StopMovingOrSizing();
		SKG_Options.xpos = SKG_Frame:GetLeft();
		SKG_Options.ypos = SKG_Frame:GetTop();
	end
end

function SKG_RaidWarnToggle(raidWarn)
	SKG_Options.raidWarn = raidWarn;
end

-- RPR G1209
function SKG_SendPositionToggle(sendPosition)
	SKG_Options.sendPosition = sendPosition;
end

-- RPR G1209
function SKG_SpamAllToggle(spamAll)
	SKG_Options.spamAll = spamAll;
end

function SKG_AllListsToggle(allLists)  --/H
	if (allLists) then
		SKG_Options.allLists = allLists;
	else
		SKG_Options.allLists = false;
	end
end
--/I
function SKG_SyncGuildToggle(syncGuild)
	if (syncGuild) then
		SKG_Options.syncGuild = syncGuild;
	else
		SKG_Options.syncGuild = false;
	end
end

function SKG_SyncRaidToggle(syncRaid)
	if (syncRaid) then
		SKG_Options.syncRaid = syncRaid;
	else
		SKG_Options.syncRaid = false;
	end
end
--/I

-- Network interface

function SKG_NetInterface_NewMaster(player)
	if (l_supervisor and UnitName("player") ~= player) then
		l_supervisor = false;
		if (l_isCaptureActive ~= 0) then
			SKG_CaptureToggle();
		end
		SKG_print(player.." is now the SK master.  You are no longer master.");
	end
end

function SKG_NetInterface_FullSync(syncLists, syncKey, reserveList)
	local major, minor = getVersionComponents();

	for listName, rollList in pairs(syncLists) do
		SavedRollLists[listName] = rollList;
	end
	SKG_SyncKey = syncKey;
	SKG_TitleText:SetText(string.format(SKG_TITLE, major, minor, syncKey));
	l_reserveList = reserveList;

	SKG_ListScrollFrame.selected = nil;
	SKG_Debug("Full sync received.  New sync key is: "..syncKey);
	if (l_loadedList and SavedRollLists[l_loadedList]) then
		l_currentRollList = SavedRollLists[l_loadedList];
		SKG_Update();
	else
		l_currentRollList = { };
		l_loadedList = nil;
		UIDropDownMenu_SetSelectedID(SKG_FrameDropDown, 0);
		UIDropDownMenu_SetText(SKG_FrameDropDown, "");  --/G
		SKG_Update();
	end
	l_undoList = { };
end

function SKG_NetInterface_DeleteList(list)
	doDeleteList(list);
end

function SKG_NetInterface_GetReservedPlayers()
	return l_reserveList;
end

function SKG_NetInterface_InsertPlayer(list,
			index, player, roll, class, frozen, insert_bottom)
	if (not SavedRollLists[list]) then
		SavedRollLists[list] = { };
	end
	local theList = SavedRollLists[list];

	if (#theList < (index - 1)) then
		SKG_Net_RequestSync();
	else
		local undoToken = { undo =
			function(this)
				table.remove(this.rollList, this.listIndex);
			end,
			rollList = theList,
			rollIndex = index,
			actionName = SKG_format(SKG_FMT_ACTION_ADD, player)
		};
		addUndo(undoToken);

		table.insert(theList, index,
			{
				player = player,
				roll = roll,
				class = class,   --/J
				frozen = frozen,
				insert_bottom = insert_bottom
			});
		SKG_Update();
	end
end

function SKG_NetInterface_NewList(list)
	doNewList(list);
end

function SKG_NetInterface_MovePlayer(list, oldIdx, newIdx)
	if (not SavedRollLists[list]) then
		SKG_Net_RequestSync();
	else
		doMoveImpl(SavedRollLists[list], oldIdx, newIdx - oldIdx);
	end
end

function SKG_NetInterface_ReservePlayer(player, reserve)
	if (reserve) then
		l_reserveList[player] = true;
	else
		l_reserveList[player] = nil;
	end
end

function SKG_NetInterface_SuicidePlayer(list, index)
	doSuicide(index, list);
end
 
function SKG_NetInterface_Undo()
	doUndo();
end

function SKG_NetInterface_RemovePlayer(list, index)
	doRemovePlayer(list, index);
end

function SKG_NetInterface_VerboseSuicide(list, movingPlayers)
	if (not SavedRollLists[list]) then
		SKG_Net_RequestSync();
	else
		local theList = SavedRollLists[list];
		local undoToken = { undo =
			function(this)
				for idx=1, #this.rollList, 1 do
					this.rollList[idx] = nil;
				end
				for _, rollInfo in ipairs(this.oldList) do
					table.insert(this.rollList, rollInfo);
				end
			end,
			rollList = theList,
			oldList = copyRollList(theList),
			actionName = SKG_format(SKG_FMT_ACTION_SUICIDE,
								theList[movingPlayers[1]].player)
		};
      
		addUndo(undoToken);

		local theFirst = theList[movingPlayers[1]];

		for idx, playerIndex in ipairs(movingPlayers) do
			if (idx < #movingPlayers) then
				theList[playerIndex] = theList[movingPlayers[idx + 1]];
			else
				theList[playerIndex] = theFirst;
			end
		end
		SKG_sortRolls(theList);
		SKG_Update();
	end
end